package br.com.winesys.geta.databinding.component.table;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.apache.commons.beanutils.PropertyUtils;

public abstract class TableModel<Entity> extends AbstractTableModel {

	private List<Entity> values = new LinkedList<Entity>();
	private List<Column> columns = new LinkedList<Column>();
	private List<Entity> added = new LinkedList<Entity>();
	private Set<Entity> changed = new HashSet<Entity>();
	private boolean editing;

	public TableModel() {
		this.createColumns();
	}

	protected abstract void createColumns();

	public void setValues(List<Entity> values) {
		this.values.clear();
		if (values != null) {
			this.values.addAll(values);
		}
		this.fireTableDataChanged();
	}

	public List<Entity> getValues() {
		return this.values;
	}

	public void addValue(Entity obj) {
		this.values.add(obj);
		this.added.add(obj);
		this.fireTableRowsInserted(this.values.size() - 1, this.values.size() - 1);
	}

	public void removeValue(Entity obj) {
		int index = this.values.indexOf(obj);
		if (this.values.remove(obj)) {
			this.fireTableRowsDeleted(index, index);
		}
	}

	public Entity getValue(int index) {
		if (this.values.size() > index) {
			return this.values.get(index);
		}
		return null;
	}

	protected void addColumn(Column column) {
		this.columns.add(column);
		this.fireTableStructureChanged();
	}

	@Override
	public String getColumnName(int columnIndex) {
		return this.columns.get(columnIndex).getName();
	}

	public int getColumnWidth(int columnIndex) {
		return this.columns.get(columnIndex).getWidth();
	}

	@Override
	public int getRowCount() {
		return this.values.size();
	}

	@Override
	public int getColumnCount() {
		return this.columns.size();
	}

	@Override
	public Object getValueAt(int row, int col) {
		if (row >= this.values.size()) {
			return null;
		}
		Object objeto = this.values.get(row);
		if (objeto == null) {
			return null;
		}
		try {
			Object value = PropertyUtils.getProperty(objeto, this.columns.get(col).getProperty());
			return value;
		} catch (Exception ex) {
			throw new RuntimeException(String.format("Não foi possível recuperar o valor da coluna %s na linha %s", this.columns.get(col).getName(),
					row + 1), ex);
		}
	}

	@Override
	public void setValueAt(Object aValue, int row, int col) {
		if (row >= this.values.size()) {
			return;
		}
		Entity objeto = this.values.get(row);
		if (objeto == null) {
			return;
		}
		try {
			String columnName = this.columns.get(col).getProperty();
			PropertyUtils.setProperty(objeto, columnName, aValue);
		} catch (Exception ex) {
			throw new RuntimeException(String.format("Não foi possível alterar valor da coluna %s na linha %s", this.columns.get(col).getName(),
					row + 1), ex);
		}
		this.changed.add(objeto);
		this.fireTableCellUpdated(row, col);
	}

	@Override
	public boolean isCellEditable(int row, int col) {
		return this.editing && this.columns.get(col).isEditable();
	}

	public TableColumnModel getColumnModel() {
		DefaultTableColumnModel model = new DefaultTableColumnModel();
		for (int i = 0; i < this.columns.size(); i++) {
			Column column = this.columns.get(i);
			TableColumn tableColumn = new TableColumn(i, column.getWidth(), column.getRenderer(), column.getEditor());
			tableColumn.setHeaderValue(column.getName());
			model.addColumn(tableColumn);
		}
		return model;
	}

	public void setEditing(boolean editing) {
		this.editing = editing;
		this.added.clear();
		this.changed.clear();
	}

	public List<Entity> getAdded() {
		return this.added;
	}

	public Set<Entity> getChanged() {
		return this.changed;
	}

	public int indexOf(Entity value) {
		return this.values.indexOf(value);
	}
}